/** @file    FestivalBundle.cpp
 *  @author  Alessandro Polo
 *  @version $Id: FestivalBundle.cpp 2821 2010-08-01 11:44:26Z alex $
 *  @brief
 * File containing methods for the wosh::services::FestivalBundle class.
 * The header for this class can be found in FestivalBundle.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/

 #include "FestivalBundle.h"

 #include "FestivalImpl.h"

 #include <core/ObjectAllocator.h>
 #include <core/MethodsCommon.h>
 #include <core/MessageProcessingCommon.h>
 #include <core/Fact.h>
 #include <core/Table.h>
 #include <core/FileSystem.h>
 #include <core/SystemError.h>


using namespace std;
namespace wosh {
 namespace services {

 WOSH_REGISTER(wosh::services::FestivalBundle, "wosh::interfaces::services::Festival", _Festival_VERSION, _static_FestivalBundle1 )
 WOSH_REGISTER(wosh::services::FestivalBundle, "wosh::BundleGeneric", _Festival_VERSION, _static_FestivalBundle2 )

FestivalBundle* FestivalBundle::ms_FestivalBundle_instance = NULL;
FestivalBundle* FestivalBundle::getInstance() { return FestivalBundle::ms_FestivalBundle_instance; }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

FestivalBundle::FestivalBundle()
	: BundleGeneric() {
	BundleGeneric::setName( _Festival_NAME, false );
	Properties.update( _KEY_Version, _Festival_VERSION );

//Log.setLevel(LOG_VERBOSE);

	Log(LOG_DEBUG, " Configuring Festival worker.." );
	this->autoWorker = new FestivalImpl(*this);

	Log(LOG_DEBUG, " Setting default properties and permissions.." );
	Properties.set( _InterpreterService_KEY_InputTypes,		(int)Notification::TYPE_TEXTUAL, Permission(Permission::Read) );
	Properties.set( _InterpreterService_KEY_OutputTypes,	(int)Notification::TYPE_AUDIO, Permission(Permission::Read) );
	Properties.set( _InterpreterService_KEY_InputLanguages,	"en_GB", Permission(Permission::Read) );
	Properties.set( _InterpreterService_KEY_OutputLanguages,"en_GB", Permission(Permission::Read) );
	Properties.set( _InterpreterService_KEY_InputProtocols,	(int)Notification::PROTOCOL_SHELL + (int)Notification::PROTOCOL_HUMAN, Permission(Permission::Read) );
	Properties.set( _InterpreterService_KEY_OutputProtocols,(int)Notification::PROTOCOL_SHELL + (int)Notification::PROTOCOL_HUMAN, Permission(Permission::Read) );

	Properties.set( _Festival_KEY_VoiceDefault, "", Permission(Permission::Read) );
	Properties.set( _Festival_KEY_VoiceCurrent, "", Permission(Permission::Read) );
	Properties.monitor(_Festival_KEY_VoiceDefault);
	Properties.monitor(_Festival_KEY_VoiceCurrent);

	Log(LOG_DEBUG, " Registering methods.." );
	MethodMessageResponse* mmConvert = Methods.create<MethodMessageResponse>( _InterpreterService_METHOD_convert, "convert message" );
	mmConvert->setMethod( this, (MethodMessageResponsePtr)&FestivalBundle::mmDoConvert );
	mmConvert->getPermission().setMask( Permission::RX, Permission::RX, Permission::RX );

	MethodMessageResponse* mmListVoices = Methods.create<MethodMessageResponse>( _METHOD_List, "List Voices (names)" );
	mmListVoices->setMethod( this, (MethodMessageResponsePtr)&FestivalBundle::mmDoListVoices );
	mmListVoices->getPermission().setMask( Permission::RX, Permission::RX, Permission::RX );

	MethodMessageResponse* mmEvalCommand = Methods.create<MethodMessageResponse>( _Festival_METHOD_evalcmd, "eval command (asynch)" );
	mmEvalCommand->setMethod( this, (MethodMessageResponsePtr)&FestivalBundle::mmDoEvalCommand );
	mmEvalCommand->getPermission().setMask( Permission::RX, Permission::RX, Permission::RX );

	FestivalBundle::ms_FestivalBundle_instance = this;

	setBundleState(Bundle::STATE_CREATED, false);
 }


FestivalBundle::~FestivalBundle() {
	Log(LOG_INFO, " Destroying.." );
	if ( isBundleRunning() ) {
		Log(LOG_WARNING, "~FestivalBundle() : Destroying while RUNNING! Trying to stop.." );
		bundleStop();
	 }
	FestivalBundle::ms_FestivalBundle_instance = NULL;
	delete this->autoWorker; this->autoWorker = NULL;
	Log(LOG_VERBOSE, ":~FestivalBundle() : Destroyed." );
 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////// BUNDLE CONTROL

WRESULT FestivalBundle::bundleStart() {
	if ( !BundleGeneric::bundleValidate_StartStop(Bundle::STATE_STARTING) ) return WRET_ERR_WRONG_STATE;

	WRESULT ret = BundleGeneric::start_SynchThread( this->autoWorker );
	// BUNDLE-STATE (STARTED) will be updated async by WORKER, through call: thread_event()

	Response* response = new Response( _METHOD_ListProperties, Properties.toTable(false) );
	Message* message = new Message(response);
	message->setSource(this);
	message->getDestination().fromString("*:Communication");
	signMessage(message);
	BusCore.postMessage(message);

	return ret;
 }

WRESULT FestivalBundle::bundleStop() {
	if ( !BundleGeneric::bundleValidate_StartStop(Bundle::STATE_STOPPING) ) return WRET_ERR_WRONG_STATE;

	WRESULT ret = BundleGeneric::stop_SynchThread( this->autoWorker );

	// BUNDLE-STATE (STOPPED) will be updated async by WORKER, through call: thread_event()
	return ret;
 }

////////////////////////////////////////////////////////////////////////////////////////////// BUNDLE CONTROL
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////// PROCESS BUS MESSAGES

void FestivalBundle::busMessage( const Message& message, const Bus* source ) {
	if ( message.isEmpty() ) return;
	BundleGeneric::busMessage(message, source);
 }

//////////////////////////////////////////////////////////////////////////////////////// PROCESS BUS MESSAGES
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENT

bool FestivalBundle::updatingProperty( bool& do_update, const Variant& value_proposed, Property& property_current, const PropertiesProvider* source )
 {
	if ( property_current.getKey() == _Festival_KEY_VoiceDefault ) {
		return false;
	 }
	else if ( property_current.getKey() == _Festival_KEY_VoiceCurrent ) {
		return false;
	 }
	else // forward processing to base class
		return BundleGeneric::updatingProperty(do_update, value_proposed, property_current, source);
 }

////////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENT
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool FestivalBundle::validateTranslation( const NotificationConversion* conversion ) {
	if ( conversion == NULL ) return false;
	if ( !conversion->hasOriginal() ) return false;
	const Notification* notif = conversion->getOriginal();

	// verify i'm able to convert
	if ( conversion->getNotificationType() != Notification::TYPE_UNKNOWN && conversion->getNotificationType() != Notification::TYPE_AUDIO ) {
		Log(LOG_CRITICAL, ":translate(%ld) : Invalid Target type [%d]", notif->getID(), (int)conversion->getNotificationType() );
		return false;
	 }
	if ( notif->getNotificationType() != Notification::TYPE_TEXTUAL ) {
		Log(LOG_CRITICAL, ":translate(%ld) : Invalid Input type [%d=%d]", notif->getID(), -1, (int)conversion->getNotificationType() );
		return false;
	 }

	// i'm not able to translate, and i understand english language
	if ( notif->getLanguage() != "en_GB" ) {
		Log(LOG_CRITICAL, ":translate(%ld) : Invalid Input Language [%s!=en_GB => %s]", notif->getID(), notif->getLanguage().c_str(), conversion->getLanguage().c_str() );
		return false;
	 }
	if ( !conversion->getLanguage().empty() && conversion->getLanguage() != "en_GB" ) {
		Log(LOG_CRITICAL, ":translate(%ld) : Invalid Output Language [%s => %s!=en_GB]", notif->getID(), notif->getLanguage().c_str(), conversion->getLanguage().c_str() );
		return false;
	 }

	if ( conversion->getOriginal()->getPlain().empty() ) {
		Log(LOG_CRITICAL, ":translate(%ld) : Text is EMPTY", notif->getID() );
		return false;
	 }

	return true;
 }

WRESULT FestivalBundle::enqueueTranslation( const NotificationConversion* conversion, const URI* uri ) {
	if ( conversion == NULL ) return WRET_ERR_PARAM;
	FestivalJob* job = new FestivalJob();
	job->conversion = conversion->clone();
	job->requestSource = *uri;
	this->autoWorker->enqueue(job);
	return WRET_OK;
 }
 
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////// METHODS

Response* FestivalBundle::mmDoConvert( const Message* requestMessage ) {
	if ( requestMessage == NULL || requestMessage->isEmpty() ) return NULL;
	if ( !requestMessage->getContent()->isRequest() ) return NULL;
	const Request* request = requestMessage->getContent()->asRequest();
	if ( !request->hasData() || !request->getData()->isKindOf<NotificationConversion>() )
		return request->replyResponse(WRET_ERR_PARAM, "Wrong DataType: not NotificationConversion!" );
	const NotificationConversion* conv = request->getData()->as<NotificationConversion>();
	if ( !conv->hasOriginal() ) {
		return request->replyResponse(WRET_ERR_PARAM, "Original Notification missing!" );
	 }
	Log(LOG_VERBOSE, ":mmDoConvert(%ld) TYPE:%d PROTO:%d [%s]", conv->getOriginal()->getID(),
									(int)conv->getNotificationType(), (int)conv->getProtocol(), conv->getLanguage().c_str() );

	bool valid = validateTranslation( conv );
	if ( !valid )
		return request->replyResponse(WRET_ERR_PARAM, "Invalid source/conversion" );

	WRESULT ret = enqueueTranslation( conv, &requestMessage->getSource() );

	return request->replyResponse(ret, "enqueued" );
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Response* FestivalBundle::mmDoListVoices( const Message* requestMessage ) {
	if ( requestMessage == NULL || requestMessage->isEmpty() ) return NULL;
	if ( !requestMessage->getContent()->isRequest() ) return NULL;
	const Request* request = requestMessage->getContent()->asRequest();
	if ( request == NULL ) return NULL;
	string option = request->getArgument().toString();
	Log(LOG_VERBOSE, ":mmListVoices(%s)", option.c_str() );

	return request->replyResponse(WRET_OK, "none" );
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Response* FestivalBundle::mmDoEvalCommand( const Message* requestMessage ) {
	if ( requestMessage == NULL || requestMessage->isEmpty() ) return NULL;
	if ( !requestMessage->getContent()->isRequest() ) return NULL;
	const Request* request = requestMessage->getContent()->asRequest();
	if ( request == NULL ) return NULL;
	string command = request->getArgument().toString();
	Log(LOG_VERBOSE, ":mmEvalCommand(%s)", command.c_str() );

	FestivalJob* job = new FestivalJob();
	job->requestSource = requestMessage->getSource();
	this->autoWorker->enqueue(job);

	return request->replyResponse(WRET_OK, "enqueued" );
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

 }; // namespace services
}; // namespace wosh
